﻿namespace FileCategorizer.Engine
{
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.IO;
    using FileCategorizer.Engine.Data;

    /// <summary>
    /// The monitor class provide file system monitoring services to the
    /// categorizer.
    /// </summary>
    public class Monitor
    {
        /// <summary>
        /// The database to use for managing the directories
        /// </summary>
        private IDatabase data;

        /// <summary>
        /// This queue holds files that have not been categorized in the 
        /// database yet.
        /// </summary>
        private Queue<string> queuedFiles = new Queue<string>();

        /// <summary>
        /// This table maps directories to their watchers.
        /// </summary>
        private Hashtable watchers = new Hashtable();

        /// <summary>
        /// Initializes a new instance of the Monitor class.  The constructor
        /// requires a reference to the database.
        /// </summary>
        /// <param name="db">The database to use.</param>
        public Monitor(IDatabase db)
        {
            this.data = db;
        }

        /// <summary>
        /// Gets a value indicating whether the monitor has files in its
        /// categorization queue.
        /// </summary>
        public bool HasQueuedFiles
        {
            get { return this.queuedFiles.Count > 0; }
        }

        /// <summary>
        /// Gets the monitor's categorization file queue.
        /// </summary>
        public Queue<string> QueuedFiles
        {
            get { return this.queuedFiles; }
        }

        /// <summary>
        /// Gets the number of directories the monitor is configured to watch.
        /// </summary>
        public int Count
        {
            get { return this.watchers.Count; }
        }

        /// <summary>
        /// Adds a new directory path to the monitor.
        /// </summary>
        /// <param name="dir">The path to monitor.</param>
        /// <exception cref="FileNotFoundException">
        ///     Thrown when invalid directory information is passed to the monitor.
        /// </exception>
        public void Add(string dir)
        {
            if (File.Exists(dir) || !Directory.Exists(dir))
            {
                throw new FileNotFoundException("Invalid directory supplied to the monitor.");
            }

            this.data.AddDirectory(dir);

            FileSystemWatcher fsw = new FileSystemWatcher(dir);
            fsw.Created += new FileSystemEventHandler(fsw_Created);
            fsw.EnableRaisingEvents = true;
            this.watchers.Add(dir, fsw);
        }

        /// <summary>
        /// Gets a value indicating whether the monitor is watching a 
        /// particular directory.
        /// </summary>
        /// <param name="dir">The directory to check.</param>
        /// <returns><code>true</code> if the directory is being watched.</returns>
        public bool Contains(string dir)
        {
            StringCollection dirs = this.data.Directories;
            return dirs.Contains(dir) && this.watchers.ContainsKey(dir);
        }

        /// <summary>
        /// Empties the monitors directory monitoring list.
        /// </summary>
        public void Reset()
        {
            this.data.ClearDirectories();
            this.watchers.Clear();
        }

        /// <summary>
        /// Removes the given directory from the monitor.
        /// </summary>
        /// <param name="dir">The path to remove.</param>
        public void Remove(string dir)
        {
            this.data.RemoveDirectory(dir);
            this.watchers.Remove(dir);
        }

        /// <summary>
        /// Handles file creation events in a monitored directory.
        /// </summary>
        /// <param name="sender">The monitor that triggered the event.</param>
        /// <param name="e">Additional event data.</param>
        private void fsw_Created(object sender, FileSystemEventArgs e)
        {
            // only process file actions
            if (File.Exists(e.FullPath))
            {
                this.queuedFiles.Enqueue(e.FullPath);
            }
        }
    }
}
